/***********************************************************************************************************************
 *该模块只负责代码的编译工作
 *主要逻辑：
 *1.main函数传递给我一个文件名，例如code，函数首先创建一个子进程准备提供编译服务
 *2.子进程在进行程序替换之前先将标准错误进行重定向，调用dup2将stderr重定向到code.stderr文件（文件名由函数生成）
 *3.子进程调用excelp函数实现程序替换，调用g++进行代码的编译工作，其中会采用unit模块中的字符拼接函数将code
 *  生成code.cpp的源文件名称（目录temp下必须要存在code.cpp文件）
 *4.主进程进行等待，等待子进程的返回结果，并且主进程会查看编译是否成功，查看方式为检验code.exe文件是否形成
 *  调用stat函数获取文件属性，获取成功则存在
 *5.编译如果出错将会把错误信息输出到temp文件夹下的code.stderr文件中
 ************************************************************************************************************************/
#pragma once
#include "../Comm/comm.hpp"
#include "../Comm/util.hpp"
#include "../Comm/log.hpp"
namespace ns_compiler
{
    using namespace ns_util;
    using namespace ns_log;

    class Compiler
    {
    public:
        Compiler()
        {
        }
        ~Compiler()
        {
        }

        // 此函数内部创建子进程进行文件的编译工作,返回编译状态
        // 返回值：编译成功：true，编译失败：false
        // 输入参数：编译的文件名：file_name
        // file_name:abcd
        // 源文件     -> ./temp/abcd.cpp
        // 可执行文件 -> ./temp/abcd.exe
        // 错误文件   -> ./temp/abcd.stderr
        static bool Compile(const std::string &file_name)
        {
            pid_t pid = fork(); // 创建子进程
            if (pid < 0)
            {
                // 创建失败
                LOG(ERROR) << "内部错误，创建子进程失败" << endl;
                return false;
            }
            else if (pid == 0)
            {
                // 子进程
                umask(0);                                                                                // 设置文件掩码
                int _stderr = open(PathUtil::CompileError(file_name).c_str(), O_CREAT | O_WRONLY, 0644); // 打开文件操作符
                if (_stderr < 0)
                {
                    // 打开文件失败
                    LOG(WARNING) << "没有成功形成stderr文件" << endl;
                    exit(OPEN_ERR);
                }
                // 将标准错误进行重定向
                dup2(_stderr, 2);
                // 子进程进行程序替换
                // 子进程调用编译器，对文件进行编译
                //  调用g++，以此种方式调用：g++ -o target src -std=c++11
                execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), PathUtil::Src(file_name).c_str(),"-D","COMPILE","-std=c++11", nullptr); // 参数以nullptr结尾，标志结束
                // 启动编译器失败
                LOG(ERROR) << "启动编译器失败，可能是参数错误" << endl;
                exit(EXECLE_ERR);
            }
            // 主进程进行等待
            waitpid(pid, nullptr, 0);
            // 编译是否成功，看是否构成可执行文件
            if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
            {
                // 编译成功
                LOG(NORMAL) << PathUtil::Src(file_name) << "编译成功！" << endl;
                return true;
            }
            LOG(ERROR) << "编译失败，未形成可执行文件" << endl;
            return false;
        }
    };
}